/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2013/02/18
* File: bounding_box.h
**/

#pragma once

#include "enn_rect.h"
#include "enn_vector3.h"

namespace enn
{
class Polyhedron;
class Frustum;
class Matrix3;
class Matrix4;
class Matrix3x4;
class Sphere;

/// Three-dimensional axis-aligned bounding box.
class BoundingBox
{
public:
	/// Construct with zero size.
	BoundingBox() :
	  min_(vec3f::ZERO),
		  max_(vec3f::ZERO),
		  defined_(false)
	  {
	  }

	  /// Copy-construct from another bounding box.
	  BoundingBox(const BoundingBox& box) :
	  min_(box.min_),
		  max_(box.max_),
		  defined_(box.defined_)
	  {
	  }

	  /// Construct from a rect, with the Z dimension left zero.
	  BoundingBox(const Rect& rect) :
	  min_(vec3f(rect.min_, 0.0f)),
		  max_(vec3f(rect.max_, 0.0f)),
		  defined_(true)
	  {
	  }

	  /// Construct from minimum and maximum vectors.
	  BoundingBox(const vec3f& min, const vec3f& max) :
	  min_(min),
		  max_(max),
		  defined_(true)
	  {
	  }

	  /// Construct from minimum and maximum floats (all dimensions same.)
	  BoundingBox(float min, float max) :
	  min_(vec3f(min, min, min)),
		  max_(vec3f(max, max, max)),
		  defined_(true)
	  {
	  }

	  /// Construct from an array of vertices.
	  BoundingBox(const vec3f* vertices, unsigned count) :
	  defined_(false)
	  {
		  Define(vertices, count);
	  }

	  /// Construct from a frustum.
	  BoundingBox(const Frustum& frustum) :
	  defined_(false)
	  {
		  Define(frustum);
	  }

	  /// Construct from a polyhedron.
	  BoundingBox(const Polyhedron& poly) :
	  defined_(false)
	  {
		  Define(poly);
	  }

	  /// Construct from a sphere.
	  BoundingBox(const Sphere& sphere) :
	  defined_(false)
	  {
		  Define(sphere);
	  }

	  /// Assign from another bounding box.
	  BoundingBox& operator = (const BoundingBox& rhs)
	  {
		  min_ = rhs.min_;
		  max_ = rhs.max_;
		  defined_ = rhs.defined_;
		  return *this;
	  }

	  /// Assign from a Rect, with the Z dimension left zero.
	  BoundingBox& operator = (const Rect& rhs)
	  {
		  min_ = vec3f(rhs.min_, 0.0f);
		  max_ = vec3f(rhs.max_, 0.0f);
		  defined_ = true;
		  return *this;
	  }

	  /// Test for equality with another bounding box.
	  bool operator == (const BoundingBox& rhs) const { return (min_ == rhs.min_ && max_ == rhs.max_); }

	  /// Test for inequality with another bounding box.
	  bool operator != (const BoundingBox& rhs) const { return (min_ != rhs.min_ || max_ != rhs.max_); }

	  /// Define from another bounding box.
	  void Define(const BoundingBox& box)
	  {
		  Define(box.min_, box.max_);
	  }

	  /// Define from a Rect.
	  void Define(const Rect& rect)
	  {
		  Define(vec3f(rect.min_, 0.0f), vec3f(rect.max_, 0.0f));
	  }

	  /// Define from minimum and maximum vectors.
	  void Define(const vec3f& min, const vec3f& max)
	  {
		  min_ = min;
		  max_ = max;
		  defined_ = true;
	  }

	  /// Define from minimum and maximum floats (all dimensions same.)
	  void Define(float min, float max)
	  {
		  min_ = vec3f(min, min, min);
		  max_ = vec3f(max, max, max);
		  defined_ = true;
	  }

	  /// Define from a point.
	  void Define(const vec3f& point)
	  {
		  min_ = max_ = point;
		  defined_ = true;
	  }

	  /// Merge a point.
	  void Merge(const vec3f& point)
	  {
		  if (!defined_)
		  {
			  min_ = max_ = point;
			  defined_ = true;
			  return;
		  }

		  if (point.x_ < min_.x_)
			  min_.x_ = point.x_;
		  if (point.y_ < min_.y_)
			  min_.y_ = point.y_;
		  if (point.z_ < min_.z_)
			  min_.z_ = point.z_;
		  if (point.x_ > max_.x_)
			  max_.x_ = point.x_;
		  if (point.y_ > max_.y_)
			  max_.y_ = point.y_;
		  if (point.z_ > max_.z_)
			  max_.z_ = point.z_;
	  }

	  /// Merge another bounding box.
	  void Merge(const BoundingBox& box)
	  {
		  if (!defined_)
		  {
			  min_ = box.min_;
			  max_ = box.max_;
			  defined_ = true;
			  return;
		  }

		  if (box.min_.x_ < min_.x_)
			  min_.x_ = box.min_.x_;
		  if (box.min_.y_ < min_.y_)
			  min_.y_ = box.min_.y_;
		  if (box.min_.z_ < min_.z_)
			  min_.z_ = box.min_.z_;
		  if (box.max_.x_ > max_.x_)
			  max_.x_ = box.max_.x_;
		  if (box.max_.y_ > max_.y_)
			  max_.y_ = box.max_.y_;
		  if (box.max_.z_ > max_.z_)
			  max_.z_ = box.max_.z_;
	  }

	  /// Define from an array of vertices.
	  void Define(const vec3f* vertices, unsigned count);
	  /// Define from a frustum.
	  void Define(const Frustum& frustum);
	  /// Define from a polyhedron.
	  void Define(const Polyhedron& poly);
	  /// Define from a sphere.
	  void Define(const Sphere& sphere);
	  /// Merge an array of vertices.
	  void Merge(const vec3f* vertices, unsigned count);
	  /// Merge a frustum.
	  void Merge(const Frustum& frustum);
	  /// Merge a polyhedron.
	  void Merge(const Polyhedron& poly);
	  /// Merge a sphere.
	  void Merge(const Sphere& sphere);
	  /// Clip with another bounding box.
	  void Clip(const BoundingBox& box);
	  /// Transform with a 3x3 matrix.
	  void Transform(const Matrix3& transform);
	  /// Transform with a 3x4 matrix.
	  void Transform(const Matrix3x4& transform);

	  void Transform(const Matrix4& transform);

	  /// Clear to undefined state.
	  void Clear()
	  {
		  min_ = vec3f::ZERO;
		  max_ = vec3f::ZERO;
		  defined_ = false;
	  }

	  /// Return center.
	  vec3f Center() const { return (max_ + min_) * 0.5f; }
	  /// Return size.
	  vec3f Size() const { return max_ - min_; }
	  /// Return half-size.
	  vec3f HalfSize() const { return (max_ - min_) * 0.5f; }

	  /// Return transformed by a 3x3 matrix.
	  BoundingBox Transformed(const Matrix3& transform) const;
	  /// Return transformed by a 3x4 matrix.
	  BoundingBox Transformed(const Matrix3x4& transform) const;

	  BoundingBox Transformed(const Matrix4& transform) const;

	  /// Return projected by a 4x4 projection matrix.
	  Rect Projected(const Matrix4& projection) const;

	  /// Test if a point is inside.
	  Intersection IsInside(const vec3f& point) const
	  {
		  if (point.x_ < min_.x_ || point.x_ > max_.x_ || point.y_ < min_.y_ || point.y_ > max_.y_ ||
			  point.z_ < min_.z_ || point.z_ > max_.z_)
			  return OUTSIDE;
		  else
			  return INSIDE;
	  }

	  /// Test if another bounding box is inside, outside or intersects.
	  Intersection IsInside(const BoundingBox& box) const
	  {
		  if (box.max_.x_ < min_.x_ || box.min_.x_ > max_.x_ || box.max_.y_ < min_.y_ || box.min_.y_ > max_.y_ ||
			  box.max_.z_ < min_.z_ || box.min_.z_ > max_.z_)
			  return OUTSIDE;
		  else if (box.min_.x_ < min_.x_ || box.max_.x_ > max_.x_ || box.min_.y_ < min_.y_ || box.max_.y_ > max_.y_ ||
			  box.min_.z_ < min_.z_ || box.max_.z_ > max_.z_)
			  return INTERSECTS;
		  else
			  return INSIDE;
	  }

	  /// Test if another bounding box is (partially) inside or outside.
	  Intersection IsInsideFast(const BoundingBox& box) const
	  {
		  if (box.max_.x_ < min_.x_ || box.min_.x_ > max_.x_ || box.max_.y_ < min_.y_ || box.min_.y_ > max_.y_ ||
			  box.max_.z_ < min_.z_ || box.min_.z_ > max_.z_)
			  return OUTSIDE;
		  else
			  return INSIDE;
	  }

	  /// Test if a sphere is inside, outside or intersects.
	  Intersection IsInside(const Sphere& sphere) const;
	  /// Test if a sphere is (partially) inside or outside.
	  Intersection IsInsideFast(const Sphere& sphere) const;

	  /// Minimum vector.
	  vec3f min_;
	  /// Maximum vector.
	  vec3f max_;
	  /// Defined flag.
	  bool defined_;
};

}